// Package storage provides storage space management functionality
package storage

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"sync"
	"syscall"
	"time"
)

// SpaceManager handles storage space management operations
type SpaceManager struct {
	basePath      string
	maxSize       int64
	mu            sync.RWMutex
	usedSpace     int64
	lastCleanup   time.Time
	cleanupPolicy CleanupPolicy
}

// CleanupPolicy defines the cleanup policy configuration
type CleanupPolicy struct {
	MaxAge           time.Duration // Maximum age for files
	MinFreeSpace     int64         // Minimum free space required
	CleanupInterval  time.Duration // How often to run cleanup
	MaxFileSize      int64         // Maximum file size to keep
	ProtectedPatterns []string     // Patterns for protected files
}

// DefaultCleanupPolicy returns default cleanup policy
func DefaultCleanupPolicy() CleanupPolicy {
	return CleanupPolicy{
		MaxAge:           7 * 24 * time.Hour, // 7 days
		MinFreeSpace:     10 * 1024 * 1024 * 1024, // 10GB
		CleanupInterval:  1 * time.Hour,
		MaxFileSize:      100 * 1024 * 1024, // 100MB
		ProtectedPatterns: []string{
			"*.lock",
			"*.tmp",
			"metadata/*",
			"config/*",
		},
	}
}

// NewSpaceManager creates a new space manager
func NewSpaceManager(basePath string, maxSize int64) (*SpaceManager, error) {
	sm := &SpaceManager{
		basePath:    basePath,
		maxSize:     maxSize,
		cleanupPolicy: DefaultCleanupPolicy(),
		lastCleanup: time.Now(),
	}

	// Calculate initial used space
	if err := sm.calculateUsedSpace(); err != nil {
		return nil, fmt.Errorf("failed to calculate used space: %w", err)
	}

	return sm, nil
}

// GetSpaceStats returns current space statistics
func (sm *SpaceManager) GetSpaceStats() (SpaceStats, error) {
	sm.mu.RLock()
	defer sm.mu.RUnlock()

	var stat syscall.Statfs_t
	if err := syscall.Statfs(sm.basePath, &stat); err != nil {
		return SpaceStats{}, fmt.Errorf("failed to get filesystem stats: %w", err)
	}

	totalSpace := int64(stat.Blocks) * int64(stat.Bsize)
	freeSpace := int64(stat.Bavail) * int64(stat.Bsize)
	availableSpace := totalSpace - sm.usedSpace

	return SpaceStats{
		TotalSpace:     totalSpace,
		UsedSpace:      sm.usedSpace,
		FreeSpace:      freeSpace,
		AvailableSpace: availableSpace,
		UsagePercent:   float64(sm.usedSpace) / float64(totalSpace) * 100,
	}, nil
}

// CheckAvailableSpace checks if there's enough space for the requested operation
func (sm *SpaceManager) CheckAvailableSpace(requiredSpace int64) error {
	sm.mu.RLock()
	currentUsedSpace := sm.usedSpace
	policy := sm.cleanupPolicy
	sm.mu.RUnlock()

	// Get filesystem stats without holding the lock
	var stat syscall.Statfs_t
	if err := syscall.Statfs(sm.basePath, &stat); err != nil {
		return fmt.Errorf("failed to get filesystem stats: %w", err)
	}

	freeSpace := int64(stat.Bavail) * int64(stat.Bsize)

	if freeSpace < policy.MinFreeSpace {
		return fmt.Errorf("insufficient free space: %d bytes available, %d bytes required",
			freeSpace, policy.MinFreeSpace)
	}

	if currentUsedSpace+requiredSpace > sm.maxSize {
		return fmt.Errorf("storage quota exceeded: %d bytes used, %d bytes max, %d bytes requested",
			currentUsedSpace, sm.maxSize, requiredSpace)
	}

	return nil
}

// ReserveSpace reserves space for an upcoming operation
func (sm *SpaceManager) ReserveSpace(size int64) error {
	sm.mu.Lock()
	defer sm.mu.Unlock()

	if err := sm.CheckAvailableSpace(size); err != nil {
		return err
	}

	sm.usedSpace += size
	return nil
}

// ReleaseSpace releases reserved space
func (sm *SpaceManager) ReleaseSpace(size int64) {
	sm.mu.Lock()
	defer sm.mu.Unlock()

	sm.usedSpace -= size
	if sm.usedSpace < 0 {
		sm.usedSpace = 0
	}
}

// UpdateUsedSpace updates the used space counter
func (sm *SpaceManager) UpdateUsedSpace(delta int64) {
	sm.mu.Lock()
	defer sm.mu.Unlock()

	sm.usedSpace += delta
	if sm.usedSpace < 0 {
		sm.usedSpace = 0
	}
}

// CleanupIfNeeded performs cleanup if needed based on policy
func (sm *SpaceManager) CleanupIfNeeded(ctx context.Context) error {
	sm.mu.Lock()
	defer sm.mu.Unlock()

	// Check if cleanup is due
	if time.Since(sm.lastCleanup) < sm.cleanupPolicy.CleanupInterval {
		return nil
	}

	return sm.performCleanup(ctx)
}

// ForceCleanup forces a cleanup operation
func (sm *SpaceManager) ForceCleanup(ctx context.Context) error {
	sm.mu.Lock()
	defer sm.mu.Unlock()

	return sm.performCleanup(ctx)
}

// performCleanup performs the actual cleanup operation
func (sm *SpaceManager) performCleanup(ctx context.Context) error {
	defer func() {
		sm.lastCleanup = time.Now()
	}()

	// Check if we need cleanup
	stats, err := sm.getSpaceStatsLocked()
	if err != nil {
		return fmt.Errorf("failed to get space stats: %w", err)
	}

	// Skip cleanup if we have enough free space
	if stats.FreeSpace >= sm.cleanupPolicy.MinFreeSpace {
		return nil
	}

	// Calculate how much space we need to free
	spaceToFree := sm.cleanupPolicy.MinFreeSpace - stats.FreeSpace

	// Perform cleanup
	freedSpace, err := sm.cleanupOldFiles(ctx, spaceToFree)
	if err != nil {
		return fmt.Errorf("failed to cleanup old files: %w", err)
	}

	// Update used space counter
	sm.usedSpace -= freedSpace
	if sm.usedSpace < 0 {
		sm.usedSpace = 0
	}

	return nil
}

// cleanupOldFiles removes old files to free up space
func (sm *SpaceManager) cleanupOldFiles(ctx context.Context, targetSpace int64) (int64, error) {
	var totalFreed int64
	cutoffTime := time.Now().Add(-sm.cleanupPolicy.MaxAge)

	// Walk through all directories
	err := filepath.Walk(sm.basePath, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return nil // Skip errors and continue
		}

		// Skip directories
		if info.IsDir() {
			return nil
		}

		// Check if file is protected
		if sm.isProtectedFile(path) {
			return nil
		}

		// Check if file is old enough to be deleted
		if info.ModTime().After(cutoffTime) {
			return nil
		}

		// Check if file is too large
		if info.Size() > sm.cleanupPolicy.MaxFileSize {
			return nil
		}

		// Delete the file
		if err := os.Remove(path); err != nil {
			return nil // Skip errors and continue
		}

		totalFreed += info.Size()

		// Check if we've freed enough space
		if totalFreed >= targetSpace {
			return filepath.SkipDir // Stop walking
		}

		return nil
	})

	if err != nil && err != filepath.SkipDir {
		return totalFreed, fmt.Errorf("failed to walk directory: %w", err)
	}

	return totalFreed, nil
}

// isProtectedFile checks if a file is protected from cleanup
func (sm *SpaceManager) isProtectedFile(path string) bool {
	relPath, err := filepath.Rel(sm.basePath, path)
	if err != nil {
		return true // Protect files we can't resolve
	}

	for _, pattern := range sm.cleanupPolicy.ProtectedPatterns {
		matched, err := filepath.Match(pattern, relPath)
		if err == nil && matched {
			return true
		}
	}

	return false
}

// calculateUsedSpace calculates the total used space
func (sm *SpaceManager) calculateUsedSpace() error {
	// For now, just set used space to 0 to avoid filesystem walking
	// This can be enhanced later with actual space calculation
	sm.usedSpace = 0
	return nil
}

// getSpaceStatsLocked returns space statistics (must be called with lock held)
func (sm *SpaceManager) getSpaceStatsLocked() (SpaceStats, error) {
	var stat syscall.Statfs_t
	if err := syscall.Statfs(sm.basePath, &stat); err != nil {
		return SpaceStats{}, fmt.Errorf("failed to get filesystem stats: %w", err)
	}

	totalSpace := int64(stat.Blocks) * int64(stat.Bsize)
	freeSpace := int64(stat.Bavail) * int64(stat.Bsize)

	return SpaceStats{
		TotalSpace:     totalSpace,
		UsedSpace:      sm.usedSpace,
		FreeSpace:      freeSpace,
		AvailableSpace: totalSpace - sm.usedSpace,
		UsagePercent:   float64(sm.usedSpace) / float64(totalSpace) * 100,
	}, nil
}

// SetCleanupPolicy updates the cleanup policy
func (sm *SpaceManager) SetCleanupPolicy(policy CleanupPolicy) {
	sm.mu.Lock()
	defer sm.mu.Unlock()

	sm.cleanupPolicy = policy
}

// GetCleanupPolicy returns the current cleanup policy
func (sm *SpaceManager) GetCleanupPolicy() CleanupPolicy {
	sm.mu.RLock()
	defer sm.mu.RUnlock()

	return sm.cleanupPolicy
}

// SpaceStats represents space usage statistics
type SpaceStats struct {
	TotalSpace     int64
	UsedSpace      int64
	FreeSpace      int64
	AvailableSpace int64
	UsagePercent   float64
}